﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;

namespace Perceiveit.Xml
{
    public class XPropertyComplex<T> : XProperty<T> where T : XEntity
    {
        private XFactory<T> _factory;

        protected XFactory<T> Factory
        {
            get { return _factory; }
        }

        public XPropertyComplex(XFactory<T> factory)
        {
            if (null == factory)
                throw new ArgumentNullException("factory");

            _factory = factory;
        }

        public override T GetValue(XEntity entity)
        {
            XPropertyCell<T> tcell;
            if (!this.TryGetCell(entity, out tcell))
            {
                XElement ele = this.GetXElement(entity, false);
                if (null == ele)
                    return null;
                else
                {
                    T value = this.CreateInstance(ele);
                    value.Init(ele);
                    this.RegisterNewCell(entity, ele, value);
                    return value;
                }
            }
            else
                return tcell.Value;
        }

        protected T CreateInstance(XElement ele)
        {
            XFactory<T> fact;
            if (this.Name == null && Factory.IsKnownName(null, out fact))
                return fact.GetEntityForName(null);
            else if (this.Name != null && Factory.IsKnownName(ele.Name, out fact))
                return fact.GetEntityForName(ele.Name);
            else
                throw new ArgumentException("The element '" + ele.Name.ToString() + "' is not a known element for this entity");
        }

        public override void SetValue(XEntity entity, T value)
        {
            XElement parent = entity.Element;
            XPropertyCell<T> tcell;
            XElement current = null;
            if (this.TryGetCell(entity, out tcell) && null != tcell)
            {
                //we have an existing reference to the underlying data
                //so we need to remove it from the document
                current = tcell.Node as XElement;
            }
            else
            {
                current = this.GetXElement(entity, false);
            }

            if (null != value)
            {
                //we now need to import the node
                XElement newele = null;
                XName name = this.Name;
                
                if (value.HasExistingNode)
                {
                    
                    newele = value.Element;
                    if (value.IsInitialized)
                    {
                        if (null != current)
                            current.ReplaceWith(newele);
                        else
                            parent.Add(newele);
                    }
                    else if (value.HasExistingNode)
                    {
                        //not initialized, but has existing nodes
                        //that need to be copied across.
                        newele = new XElement(name, value.Element.Nodes());

                        if (null != current)
                            current.ReplaceWith(newele);
                        else
                            parent.Add(newele);

                        if (value.Element.HasAttributes)
                            newele.ReplaceAttributes(value.Element.Attributes());
                        if (value.Element.HasElements)
                            newele.ReplaceNodes(value.Element.Nodes());

                        value.Init(newele);
                    }
                    else
                    {
                        newele = new XElement(name);

                        if (null != current)
                            current.ReplaceWith(newele);
                        else
                            parent.Add(newele);
                    }
                }
                else //no existing node on new value
                {
                    newele = new XElement(name);

                    if (null != current)
                        current.ReplaceWith(newele);
                    else
                        parent.Add(newele);

                    //initialize the value with the new node
                    //so that it now assumes this underlying identity
                    value.Init(newele);
                }

                if (null != tcell)
                {
                    tcell.Node = newele;
                    tcell.Value = value;
                }
                else
                    tcell = this.RegisterNewCell(entity, newele, value);
            }
            else if (null != current)
                current.Remove();

        }

        public override void ClearValue(XEntity entity)
        {
            this.SetValue(entity, null);
        }
    }
}
